﻿using System;
using System.Collections;
using System.ComponentModel;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Nvigorate.Aspect;
using Nvigorate.Common.Reflection;
using Nvigorate.Data;
using Nvigorate.Data.Query;
using Nvigorate.Relational.Mapping.Index;
using Nvigorate.Relational.Provider;
using Nvigorate.Relational.Repository;
using Nvigorate.Relational.State;
using Nvigorate.TestObjects;

namespace Nvigorate.Test.Relational
{
    /// <summary>
    /// Summary description for BindingMapTest
    /// </summary>
    [TestClass]
    public class BindingMapTest
    {
        #region Hide

        public BindingMapTest()
        {
            //
            // TODO: Add constructor logic here
            //

            //MapFake.PopulateMaps();
        }

        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        #region Additional test attributes
        //
        // You can use the following additional attributes as you write your tests:
        //
        // Use ClassInitialize to run code before running the first test in the class
        // [ClassInitialize()]
        // public static void MyClassInitialize(TestContext testContext) { }
        //
        // Use ClassCleanup to run code after all tests in a class have run
        // [ClassCleanup()]
        // public static void MyClassCleanup() { }
        //
        // Use TestInitialize to run code before running each test 
        // [TestInitialize()]
        // public void MyTestInitialize() { }
        //
        // Use TestCleanup to run code after each test has run
        // [TestCleanup()]
        // public void MyTestCleanup() { }
        //
        #endregion

        #endregion
        
        [TestMethod]
        public void MapTest1()
        {
            var repository = new DatabaseRepository("simple");
            repository.EnforceConcurrency = true;

            var people = repository.GetCollection<Person>(LoadInstruction<Person>.New()
                    .Page(2)
                    .PageBy(10)
                    .OrderBy(Order.By().Descending("FirstName", "Person"))
                    .LoadOnly("FirstName")
                    //.Where(Criterion.Property("FirstName").Like((Literal)"A%"))
                    .Where(criterion =>
                        criterion.Property(p => p.FirstName).Like("A%") &
                        criterion.Property(p => p.DateOfBirth))
                    );

            Assert.IsNotNull(people);
        }

        [TestMethod]
        public void MapTest2()
        {
            var repository = new DatabaseRepository("simple");
            var abigail = repository.GetInstance<Person>("111-11-1101", LoadInstruction<Person>.New().EagerLoad());

            Assert.IsTrue(abigail.FirstName == "Abigail");
            Assert.IsTrue(abigail.Vehicles != null && abigail.Vehicles.Count > 0);
        }

        [TestMethod]
        public void MapTest3()
        {
            var repository = new DatabaseRepository("simple");
            var people = repository.GetCollection<Person>(
                new LoadInstruction<Person>()
                    .Page(1)
                    .PageBy(10)
                    .EagerLoad());

            Assert.AreEqual(10, people.Count());

            foreach (var person in people)
            {
                var vehicles = repository.GetRelatives<Vehicle, Person>(person);
                if(vehicles.Count == 0)
                {
                    Assert.AreEqual(0, person.Vehicles.Count);
                }
                else
                {
                    Assert.AreEqual(person.Vehicles.Count, vehicles.Count());
                }
            }
        }

        [TestMethod]
        public void GetCollection_LoadOnlyLoadInstruction_OnlyLoadsTheSpecifiedProperty()
        {
            var repository = new DatabaseRepository("simple");
            var people = repository.GetCollection<Person>(
                new LoadInstruction<Person>()
                    .Page(1)
                    .PageBy(10)
                    .LoadOnly("FirstName")
                );

            Assert.AreEqual(people.Count(), 10);

            foreach (var person in people)
            {
                Assert.IsTrue(string.IsNullOrEmpty(person.LastName));
                Assert.IsFalse(string.IsNullOrEmpty(person.SocialSecurity)); // primary key should ALWAYS get populated
            }
        }

        [TestMethod]
        public void MapTest5()
        {
            var repository = new DatabaseRepository("simple");
            var people = repository.GetCollection<Person>(
                new LoadInstruction<Person>()
                    .Page(1)
                    .PageBy(4)
                    .LoadOnly("FirstName", "Vehicles")
                );

            Assert.AreEqual(people.Count(), 4);

            foreach (var person in people)
            {
                Assert.IsFalse(string.IsNullOrEmpty(person.FirstName));
                Assert.IsTrue(string.IsNullOrEmpty(person.LastName));
                Assert.IsFalse(string.IsNullOrEmpty(person.SocialSecurity)); // primary key should ALWAYS get populated
                Assert.IsTrue(person.Vehicles.Count > 0);
                Assert.IsTrue(person.Addresses == null);
            }
        }

        [TestMethod]
        public void CreatingAnInstanceAndRetrievingTheInstanceById_PropertiesOfBothInstancesShouldMatch()
        {
            var repository = new DatabaseRepository("simple");
            var newPerson = new Person()
                                {
                                    FirstName = "Bilbo",
                                    LastName = "Jimbo",
                                    SocialSecurity = "999-99-9990",
                                    DateOfBirth = DateTime.Parse("01/01/1955")
                                };

            try
            {
                repository.Persist(newPerson);

                var reloadTest = repository.GetInstance<Person>("999-99-9990");

                

                foreach (var property in new string[] {"FirstName", "LastName", "SocialSecurity", "DateOfBirth"})
                {
                    var newPersonValue = Reflector.Read(newPerson, property);
                    var reloadValue = Reflector.Read(reloadTest, property);

                    if (newPersonValue != null && reloadValue != null && !(newPersonValue is IEnumerable))
                    {
                        Assert.IsTrue(newPersonValue.Equals(reloadValue));
                    }
                }
            }
            finally
            {
                repository.Delete(newPerson);
            }
        }

        [TestMethod]
        public void ConcurrencyTest()
        {
            var repository = new DatabaseRepository("simple");

            var newPerson = new Person()
            {
                FirstName = "Bilbo",
                LastName = "Jimbo",
                SocialSecurity = "999-99-9990",
                DateOfBirth = DateTime.Parse("01/01/1955")
            };

            repository.Persist(newPerson);

            var reloadTest = repository.GetInstance<Person>("999-99-9990");

            reloadTest.FirstName = "Poop";
            repository.Persist(reloadTest);

            //this should fail
            try
            {
                repository.EnforceConcurrency = true;
                newPerson.FirstName = "Bigole";
                repository.Persist(newPerson);
            }
            catch (ConcurrencyException ex)
            {
                Assert.IsTrue(ex.Table == "Person");
            }
            finally
            {
                repository.Delete(newPerson);
            }
        }

        [TestMethod]
        public void AddInstanceWithChildAndUpdateChild_ShouldPersistChildChange()
        {
            var repository = new DatabaseRepository("simple");

            var newPerson = new Person()
            {
                FirstName = "Bilbo",
                LastName = "Jimbo",
                SocialSecurity = "999-99-9990",
                DateOfBirth = DateTime.Parse("01/01/1955"),
                Vehicles = new List<Vehicle>() { new Vehicle() { VIN = "019210192", Model = "Test", Year = 1901 } }
            };

            repository.Persist(newPerson);

            newPerson.Vehicles[0].Model = "CrapMobile";

            repository.Persist(newPerson);

            var reloadTest = repository.GetInstance<Person>("999-99-9990", LoadInstruction<Person>.New().EagerLoad());

            repository.Delete(newPerson, true);
        }

        [TestMethod]
        public void MovingChildObjectFromOneParentToAnotherParent_ShouldBeChangedInObjects()
        {
            var repository = new DatabaseRepository("simple");

            var originalOwner = new Person()
            {
                FirstName = "Bilbo",
                LastName = "Jimbo",
                SocialSecurity = "999-99-9990",
                DateOfBirth = DateTime.Parse("01/01/1955"),
                Vehicles = new List<Vehicle>() { new Vehicle() { VIN = "019210192", Model = "Test", Year = 1901 } }
            };

            repository.Persist(originalOwner);

            var newOwner = new Person()
                               {
                                   FirstName = "Gandalf",
                                   LastName = "Grayhame",
                                   SocialSecurity = "000-00-0000",
                                   DateOfBirth = DateTime.Parse("01/01/1800"),
                                   Vehicles = new List<Vehicle>()
                               };

            repository.Persist(newOwner);

            var vehicle = originalOwner.Vehicles[0];
            newOwner.Vehicles.Add(vehicle);

            vehicle.InstanceState.AddChangedValue("OwnerSocial", originalOwner.SocialSecurity);
            repository.Persist(newOwner);
            originalOwner.Vehicles.Clear();

            Assert.IsFalse(repository.GetRelative<Vehicle, Person>(originalOwner).Model == "Test");
            Assert.IsTrue(repository.GetRelative<Vehicle, Person>(newOwner).Model == "Test");

            repository.Delete(originalOwner, true);
            repository.Delete(newOwner, true);
        }

        [TestMethod]
        public void LoadAuthors_EagerLoadEntries()
        {
            var repository = new DatabaseRepository("simple");

            var author = repository.GetInstance(LoadInstruction<Author>.New()
                .Where(c => c.Property(a => a.Name).Like("A%"))
                .EagerLoad());

            Assert.IsNotNull(author);
            Assert.IsNotNull(author.Contributions);
        }

        [TestMethod]
        public void LoadAuthors_LazyLoadEntries()
        {
            var repository = new DatabaseRepository("simple");

            var author = repository.GetInstance(LoadInstruction<Author>.New()
                .Where(criterion => criterion.Property(a => a.Name).Like("A%")));

            Assert.IsNotNull(author);
            Assert.IsNotNull(author.Contributions);

            var article = author.Contributions[0];
            Assert.IsNotNull(article.Contributors);
        }

        [TestMethod]
        public void CreateManyToManyAndPersist()
        {
            var repository = new DatabaseRepository("simple");

            var author = new Author()
                             {
                                 Name = "Bilbo",
                                 Contributions = new StatefulList<Entry>()
                                                     {
                                                         new Entry() {Name = "Article About Dragons"},
                                                         new Entry() {Name = "Article About Rings"}
                                                     }
                             };

            repository.Persist(author);

            var newAuthor = repository.GetInstance<Author>(author.Id);

            Assert.AreEqual(author.Name, newAuthor.Name);
            Assert.AreEqual(author.Contributions.Count, newAuthor.Contributions.Count);

            repository.Delete(newAuthor, true);

            var emptyAuthor = repository.GetInstance<Author>(author.Id);

            Assert.AreEqual(default(int), emptyAuthor.Id);
            Assert.AreEqual(default(string), emptyAuthor.Name);
        }

        [TestMethod]
        public void RemoveRelationshipFromManyToManyAndReAdd()
        {
            var repository = new DatabaseRepository("simple");

            var initialAuthor = new Author()
            {
                Name = "Bilbo",
                Contributions = new StatefulList<Entry>()
                                                     {
                                                         new Entry() {Name = "Article About Dragons"},
                                                         new Entry() {Name = "Article About Rings"}
                                                     }
            };

            repository.Persist(initialAuthor);

            var author = repository.GetInstance(
                LoadInstruction<Author>.New()
                    .Where(criterion => criterion.Property(a => a.Name).EqualTo("Bilbo"))
                    .EagerLoad());

            author.Contributions.Remove(author.Contributions.FirstOrDefault(c => c.Name == "Article About Rings"));

            repository.Persist(author);

            Assert.AreEqual(1, author.Contributions.Count);

            var entry = repository.GetInstance(
                    LoadInstruction<Entry>.New()
                    .Where(criterion => criterion.Property(e => e.Name).EqualTo("Article About Rings"))
                );

            author.Contributions.Add(entry);

            repository.Persist(author);

            var freshAuthor = repository.GetInstance(
                LoadInstruction<Author>.New()
                    .Where(criterion => criterion.Property(a => a.Name).EqualTo("Bilbo"))
                    .EagerLoad());

            Assert.AreEqual(2, freshAuthor.Contributions.Count);

            repository.Delete(initialAuthor, true);
        }

        [TestMethod]
        public void TestSingleRecord_RecursiveRelationship()
        {
            var repository = new DatabaseRepository("simple");

            var employee = repository.GetInstance<Employee>(8);

            Assert.IsNotNull(employee);
            Assert.AreEqual(8, employee.Id);
        }

        [TestMethod]
        public void TestMapInheritence()
        {
            var index = 
                new MapIndexFactory(ConfigurationManager.GetSection("MapConfiguration") as MapConfigurationSection)
                    .CreateMapIndex()
                    .GetRepositoryMapIndex("simple");

            var employeeMap = index.GetMap(typeof (Employee));

            Assert.AreEqual(2, employeeMap.Sources.Count);

            var employeeSource = employeeMap.Sources["employee"];
            var personSource = employeeMap.Sources["Person"];

            Assert.AreEqual(4, personSource.Assignments.Count);
            Assert.AreEqual(5, employeeSource.Assignments.Count);
            Assert.IsNotNull(employeeSource.Join);
            Assert.AreEqual("Person", employeeMap.GetRootName());
        }

        [TestMethod]
        public void TestDomainPersistence()
        {
            var repository = new DatabaseRepository("simple");
            var sales = new Department()
                            {
                                Name = "Dunder Mifflin Scranton - Sales",
                                Employees = new List<Employee>()
                            };

            var michaelScott = new Employee()
                                   {
                                       FirstName = "Michael",
                                       LastName = "Scott",
                                       SocialSecurity = "400-01-0001",
                                       DateOfBirth = DateTime.Now,
                                       Department = sales,
                                       Employees = new List<Employee>()
                                   };
            var dwightShrute = new Employee()
                                   {
                                       FirstName = "Dwight",
                                       LastName = "Shrute",
                                       SocialSecurity = "400-01-0002",
                                       DateOfBirth = DateTime.Now,
                                       Manager = michaelScott,
                                       Department = sales
                                   };

            sales.Manager = michaelScott;


            try
            {
                repository.Persist(sales);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            finally
            {
                repository.Delete(sales, true);
            }

        }


        [TestMethod]
        public void Cleanup()
        {
            DeleteStuff();
        }

        [ClassCleanup]
        public static void Scrub()
        {
            MapFake.PopulateMaps();
            DeleteStuff();
        }

        [ClassInitialize]
        public static void PreScrub(TestContext context)
        {
            MapFake.PopulateMaps();
            DeleteStuff();
        }


        public static void DeleteStuff()
        {
            var repository = new DatabaseRepository("simple");
            var record = repository.GetInstance<Person>("999-99-9990", new LoadInstruction<Person>().EagerLoad());
            if (record != null)
                repository.Delete(record, true);
            record = repository.GetInstance<Person>("000-00-0000", new LoadInstruction<Person>().EagerLoad());
            if (record != null)
                repository.Delete(record, true);
            var car = repository.GetInstance<Vehicle>("019210192", new LoadInstruction<Vehicle>().EagerLoad());
            if (car != null)
                repository.Delete(car, true);
            var authorsNamedBilbo = repository.GetCollection(LoadInstruction<Author>.New().EagerLoad().Where(c => c.Property(a => a.Name).EqualTo("Bilbo")));
            repository.Delete(authorsNamedBilbo as IEnumerable<Author>, true);

            //var department = repository.GetInstance(LoadInstruction<Department>.New().EagerLoad());
            //repository.Delete(department, true);
        }
    }
}
